﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using K9Nano.IM.Models;
using K9Nano.LayIM;
using K9Nano.LayIM.Models;
using K9Nano.LayIM.Stores;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using WalkingTec.Mvvm.Core;

namespace K9Nano.IM.LayIM
{
    public class ChatFriendStore : ChatStoreBase, IChatFriendStore
    {
        public ChatFriendStore(IDataContext db, IOptions<LayIMOptions> options) : base(db, options)
        {
        }

        public async Task<IEnumerable<LayIMGroup>> GetGroupsAsync(string userId, CancellationToken cancellation)
        {
            Guid.TryParse(userId, out var uid);
            var query = await Db.Set<ChatRoomMember>()
                .Include(x => x.Room)
                .AsNoTracking()
                .Where(x => x.MemberId == uid)
                .Select(x => x.Room)
                .ToListAsync(cancellation);
            return query.Select(x => new LayIMGroup
                {
                    id = x.ID.ToString(),
                    groupname = x.Name,
                    avatar = Options.DefaultGroupAvatar
                })
                .ToList();
        }

        public async Task<IEnumerable<LayIMGroupMember>> GetFriendsAsync(string userId, CancellationToken cancellation)
        {
            Guid.TryParse(userId, out var uid);

            var query = await Db.Set<ChatGroup>()
                .Include(x => x.Members)
                .ThenInclude(m => m.Friend)
                .AsNoTracking()
                .Where(x => x.OwnerId == uid)
                .ToListAsync(cancellation);

            var result = query
                .Select(x => new LayIMGroupMember
                {
                    id = x.ID.ToString(),
                    groupname = x.Name,
                    list = x.Members.Select(m => MapToImUser(m.Friend)).ToList()
                })
                .ToList();
            return result;
        }

        public async Task<LayIMGroupMember> GetMembersAsync(string groupId, CancellationToken cancellation)
        {
            Guid.TryParse(groupId, out var uid);

            var group = await Db.Set<ChatGroup>()
                .Include(x => x.Members)
                .AsNoTracking()
                .FirstOrDefaultAsync(x => x.ID == uid, cancellationToken: cancellation);

            if (group == null)
            {
                return null;
            }

            return new LayIMGroupMember
            {
                id = group.ID.ToString(),
                groupname = group.Name,
                list = group.Members.Select(m => MapToImUser(m.Friend)).ToList()
            };
        }

        public async Task<IEnumerable<string>> GetOnlineFriendsAsync(string userId, CancellationToken cancellation)
        {
            Guid.TryParse(userId, out var uid);

            var group = await Db.Set<ChatGroupMember>()
                .Include(x => x.Group)
                .AsNoTracking()
                .Where(x => x.Group.OwnerId == uid && x.Group.Default && x.Friend.Online)
                .Select(x => x.FriendId)
                .ToListAsync(cancellation);

            return group.Select(x => x.ToString()).ToList();
        }

        public async Task<LayIMUser> GetUserAsync(string userId, CancellationToken cancellation)
        {
            Guid.TryParse(userId, out var uid);
            var user = await Db.Set<User>().FirstOrDefaultAsync(x => x.ID == uid, cancellation);
            if (user == null) return null;
            return MapToImUser(user);
        }

        public async Task<LayIMGroup> GetGroupAsync(string groupId, CancellationToken cancellation)
        {
            Guid.TryParse(groupId, out var uid);
            var group = await Db.Set<ChatGroup>().FirstOrDefaultAsync(x => x.ID == uid, cancellation);
            if (group == null) return null;
            return new LayIMGroup
            {
                id = group.ID.ToString(),
                groupname = group.Name,
                avatar = group.Avatar
            };
        }

        public async Task OnlineAsync(string userId, CancellationToken cancellation)
        {
            Guid.TryParse(userId, out var uid);
            var user = await Db.Set<User>().FirstOrDefaultAsync(x => x.ID == uid && !x.Online, cancellation);
            if (user == null) return;
            user.Online = true;
            await Db.SaveChangesAsync(cancellation);
        }

        public async Task OfflineAsync(string userId, CancellationToken cancellation)
        {
            Guid.TryParse(userId, out var uid);
            var user = await Db.Set<User>().FirstOrDefaultAsync(x => x.ID == uid && x.Online, cancellation);
            if (user == null) return;
            user.Online = false;
            await Db.SaveChangesAsync(cancellation);
        }

        public async Task<string> AddGroupAsync(string userId, LayIMGroup group, CancellationToken cancellation)
        {
            var chatGroup = new ChatGroup
            {
                Name = group.groupname,
                Avatar = group.avatar,
                OwnerId = Guid.Parse(userId)
            };
            Db.AddEntity(chatGroup);
            await Db.SaveChangesAsync(cancellation);
            return chatGroup.ID.ToString();
        }

        public async Task AddFriendToGroupAsync(string friendId, string groupId, CancellationToken cancellation)
        {
            Guid.TryParse(groupId, out var gid);
            Guid.TryParse(friendId, out var fid);
            var any = await Db.Set<ChatGroupMember>()
                .AnyAsync(x => x.GroupId == gid && x.FriendId == fid, cancellation);
            if (any) return;

            Db.AddEntity(new ChatGroupMember
            {
                GroupId = gid,
                FriendId = fid
            });
            await Db.SaveChangesAsync(cancellation);
        }

        public async Task RemoveFriendFromGroupAsync(string friendId, string groupId, CancellationToken cancellation)
        {
            Guid.TryParse(groupId, out var gid);
            Guid.TryParse(friendId, out var fid);
            var any = await Db.Set<ChatGroupMember>()
                .FirstOrDefaultAsync(x => x.GroupId == gid && x.FriendId == fid, cancellation);
            if (any == null) return;
            Db.DeleteEntity(any);
            await Db.SaveChangesAsync(cancellation);
        }

        public async Task RemoveGroupsAsync(string groupId, CancellationToken cancellation)
        {
            Guid.TryParse(groupId, out var uid);
            var group = await Db.Set<ChatGroup>().FirstOrDefaultAsync(x => x.ID == uid, cancellation);
            if (group == null) return;
            Db.DeleteEntity(group);
            await Db.SaveChangesAsync(cancellation);
        }

        public async Task<LayIMAddFriend> TryAddRoomToAppGroupAsync(string roomId, string staffId, string appId, CancellationToken cancellation)
        {
            Guid.TryParse(roomId, out var roomGid);
            var customer = await Db.Set<ChatRoom>().Include(x => x.Customer)
                .FirstOrDefaultAsync(x => x.ID == roomGid, cancellation);

            if (customer == null) return null;

            Guid.TryParse(staffId, out var staffGid);
            Guid.TryParse(appId, out var appGid);

            var group = await Db.Set<ChatGroup>()
                .FirstOrDefaultAsync(x => x.OwnerId == staffGid && x.AppId == appGid, cancellation);

            if (group == null) return null;

            if (await Db.Set<ChatGroupMember>()
                .AnyAsync(x => x.GroupId == group.ID && x.FriendId == customer.CustomerId, cancellation))
            {
                return null;
            }

            Db.AddEntity(new ChatGroupMember
            {
                GroupId = group.ID,
                FriendId = customer.CustomerId
            });

            try
            {
                await Db.SaveChangesAsync(cancellation);
            }
            catch (Exception e) when (e.Message.Contains("idx_group_member"))
            {
                // ignore
            }

            var newFriend = new LayIMAddFriend
            {
                groupid = group.ID.ToString(),
                id = customer.ID.ToString(),
                username = customer.Customer.Nickname ?? customer.Customer.Name,
                avatar = GetAvatar(customer.Customer),
                sign = customer.Customer.Sign
            };

            return newFriend;
        }
    }
}